package com.bc.plugin.doc.builder;

import com.bc.plugin.doc.dto.RequestMappingDTO;
import com.bc.plugin.doc.resolver.ApiParamResolver;
import com.bc.plugin.doc.resolver.help.JsonBuildHelper;
import com.bc.plugin.doc.resolver.param.CustomRespFieldApiParamHandle;
import com.power.common.util.CollectionUtil;
import com.power.common.util.JsonFormatUtil;
import com.power.common.util.StringUtil;
import com.power.common.util.UrlUtil;
import com.power.doc.constants.DocAnnotationConstants;
import com.power.doc.constants.DocGlobalConstants;
import com.power.doc.constants.DocTags;
import com.power.doc.model.*;
import com.power.doc.utils.DocUtil;
import com.thoughtworks.qdox.model.JavaAnnotation;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaMethod;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @author xhh 2019/11/21
 */
public class ApiMethodDocBuilder {
    private String host;
    private ApiConfig apiConfig;
    private BuilderConfig builderConfig;
    private ApiParamResolver apiParamResolver;


    /**
     * use custom config
     *
     * @param config config
     */
    public ApiMethodDocBuilder(ApiConfig config, BuilderConfig builderConfig) {
        if (null == config) {
            throw new NullPointerException("ApiConfig can't be null.");
        }
        if (StringUtil.isEmpty(config.getServerUrl())) {
            this.host = "http://{server}";
        } else {
            this.host = config.getServerUrl();
        }
        this.apiConfig = config;
        this.builderConfig = builderConfig;
        this.apiParamResolver = new ApiParamResolver(builderConfig);
    }

    public List<ApiMethodDoc> buildControllerMethod(final JavaClass cls) {
        String baseUrl = "";
        List<JavaAnnotation> classAnnotations = cls.getAnnotations();
        for (JavaAnnotation annotation : classAnnotations) {
            String annotationName = annotation.getType().getName();
            if (DocAnnotationConstants.REQUEST_MAPPING.equals(annotationName)
                    || DocGlobalConstants.REQUEST_MAPPING_FULLY.equals(annotationName)) {
                baseUrl = StringUtil.removeQuotes(annotation.getNamedParameter("value").toString());
            }
        }
        List<JavaMethod> methods = cls.getMethods();
        List<ApiMethodDoc> methodDocList = new ArrayList<>(methods.size());
        int methodOrder = 1;
        for (JavaMethod method : methods) {
            ApiMethodDoc apiMethodDoc = build(baseUrl, cls, method, methodOrder);
            if (apiMethodDoc != null) {
                methodDocList.add(apiMethodDoc);
                methodOrder++;
            }
        }
        return methodDocList;
    }

    public ApiMethodDoc build(String preUrl, JavaClass cls, JavaMethod method, int methodOrder) {
        RequestMappingDTO requestMappingDTO = builderConfig.getRequestMappingHandler().handle(host, preUrl, method);
        if (requestMappingDTO == null || null != method.getTagByName(DocGlobalConstants.IGNORE_TAG)) {
            return null;
        }
        ApiMethodDoc apiMethodDoc = new ApiMethodDoc();
        apiMethodDoc.setContentType(requestMappingDTO.getMediaType());
        if (apiMethodDoc.getContentType() == null) {
            apiMethodDoc.setContentType(apiConfig.getDefaultContentType());
        }
        apiMethodDoc.setType(requestMappingDTO.getMethodType());
        apiMethodDoc.setUrl(UrlUtil.simplifyUrl(requestMappingDTO.getUrl()));
        apiMethodDoc.setOrder(methodOrder);
        String comment = method.getComment();
        String clazzName = cls.getCanonicalName();
        if (StringUtils.isBlank(comment)) {
            comment = null;
            if (apiConfig.isStrict()) {
                throw new RuntimeException(clazzName + " method:（" + method.getName() + "） not fount comment");
            }
        }
        comment = Optional.ofNullable(comment).orElse(method.getName());
        apiMethodDoc.setDesc(comment);
        apiMethodDoc.setName(method.getName());

        String methodUid = clazzName + method.getName();
        this.handleMethodUid(apiMethodDoc, methodUid);
        String apiNoteValue = DocUtil.getNormalTagComments(method, DocTags.API_NOTE, cls.getName());
        if (StringUtil.isEmpty(apiNoteValue)) {
            apiNoteValue = method.getComment();
        }
        apiMethodDoc.setDetail(apiNoteValue);
        List<ApiReqHeader> apiReqHeaders = builderConfig.getRequestHeaderHandler().getApiReqHeaders(method,
                apiConfig.getRequestHeaders());
        // reduce create in template
        apiMethodDoc.setHeaders(createHeaders(apiReqHeaders));
        apiMethodDoc.setRequestHeaders(apiReqHeaders);

        List<ApiParam> paramListAll = builderConfig.getRequestParamHandler().getRequestParams(method, cls, apiConfig, apiParamResolver);
        apiMethodDoc.setRequestParams(paramListAll);

        apiMethodDoc.setResponseParams(builderConfig.getResponseParamHandler().getResponseParams(method, apiParamResolver));
        Map<String, CustomRespField> responseFieldMap = CustomRespFieldApiParamHandle.toMap(apiConfig.getCustomResponseFields());
        String requestJson = builderConfig.getUsageDataHandler().getRequestUsageData(requestMappingDTO.getShortUrl());
        if (requestJson == null && builderConfig.isCreateUsageData()) {
            requestJson = JsonBuildHelper.buildReqJson(method, apiMethodDoc, requestMappingDTO.isPostMethod(), responseFieldMap, builderConfig.getJavaClassFileBuilder());
        }
        apiMethodDoc.setRequestUsage(JsonFormatUtil.formatJson(requestJson));
        String responseJson = builderConfig.getUsageDataHandler().getResponseUsageData(requestMappingDTO.getShortUrl());
        if (responseJson == null && builderConfig.isCreateUsageData()) {
            responseJson = JsonBuildHelper.buildReturnJson(method, responseFieldMap, builderConfig.getJavaClassFileBuilder());
        } else {
            responseJson = JsonFormatUtil.formatJson(responseJson);
        }
        apiMethodDoc.setResponseUsage(responseJson);
        return apiMethodDoc;
    }

    /**
     * create request headers
     *
     * @param headers Api request headers
     * @return headers
     */
    private String createHeaders(List<ApiReqHeader> headers) {
        StringBuilder builder = new StringBuilder();
        if (CollectionUtil.isEmpty(headers)) {
            headers = new ArrayList<>(0);
        }
        for (ApiReqHeader header : headers) {
            builder.append(header.getName()).append("|").append(header.getType()).append("|").append(header.getDesc())
                    .append("|").append(header.isRequired()).append("|").append(header.getSince()).append("\n");
        }
        return builder.toString();
    }

    private void handleMethodUid(ApiMethodDoc methodDoc, String methodName) {
        String name = DigestUtils.md5Hex(methodName);
        int length = name.length();
        if (name.length() < 32) {
            methodDoc.setMethodId(name);
        } else {
            methodDoc.setMethodId(name.substring(length - 32, length));
        }
    }

}
